package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.product.service.TestService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.api.TimeSeriesEntry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 1.在缓存中存储num初始值为0
     * 2.利用缓存工具类
     * 3.如果num不为空，进行加1
     * 4.如果num为空，直接返回即可
     *
     *  使用步骤
     *   RLock lock = redissonClient.getLock("lock");
     *   1.lock.lock();lock.unlock(); 最常见的方式
     *   2.lock.lock(10,TimeUnit.SECONDS); 给锁设置一个过期时间
     *   3.尝试加锁，最多等待100秒，上锁10秒后自动解锁
     *   boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
     *         if (res){
     *             try {
     *
     *             }finally {
     *
     *             }
     *         }
     */
    @Override
    public void testLock() throws InterruptedException {

        RLock lock = redissonClient.getLock("lock");

        //调用方法
        //lock.lock(10,TimeUnit.SECONDS);

        boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
        if (res){
            // 上锁成功
            try {
                // 获取到当前的num值
                String num = redisTemplate.opsForValue().get("num");
                // 判断
                if (StringUtils.isEmpty(num)){
                    return;
                }
                // num不为空，需要对当前值进行+1操作，写会缓存
                int numValue = Integer.parseInt(num);
                // 写回缓存
                redisTemplate.opsForValue().set("num",String.valueOf(++numValue));
            }finally {
                // 解锁
                lock.unlock();
            }
        }
    }


    /**
     * 只有全部读的时候才不会有互斥
     * @return
     */
    @Override
    public String readLock() {
        // 创建读写锁对象
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("anyRWLock");
        // 上锁 10s自动解锁
        rwLock.readLock().lock(10, TimeUnit.SECONDS);
        String msg = redisTemplate.opsForValue().get("msg");

        return msg;
    }


    @Override
    public String writeLock() {
        // 创建读写锁对象
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("anyRWLock");
        // 上锁 10s自动解锁
        rwLock.writeLock().lock(10, TimeUnit.SECONDS);
        String uuid = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set("msg",uuid);
        return "写入完成"+uuid;
    }


    /**
     * 1.在缓存中存储num初始值为0
     * 2.利用缓存工具类
     * 3.如果num不为空，进行加1
     * 4.如果num为空，直接返回即可
     *
     * String：
     * List:
     * Hash:
     * Set:
     * ZSet:
     * 本地锁局限性：不能在微服务集群的情况下！
     */
//    @Override
//    public synchronized void testLock() {
//        // 获取到当前的num值
//        String num = redisTemplate.opsForValue().get("num");
//        // 判断
//        if (StringUtils.isEmpty(num)){
//            return;
//        }
//        // num不为空，需要对当前值进行+1操作，写会缓存
//        int numValue = Integer.parseInt(num);
//        // 写回缓存
//        redisTemplate.opsForValue().set("num",String.valueOf(++numValue));
//    }

    /**
     * 使用Redis做分布锁
     * 1.需要的基本命令：
     *      setnx:表示当key不存在的时候生效
     *      del:表示删除当前key
     *  问题：setnx刚好获取到锁，业务逻辑出现异常，导致锁无法释放
     *  解决：设置过期时间，自动释放
     *
     */
//    @Override
//    public void testLock() {
//
//        // 使用setnx命令 setnx lock ok
//        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "ok");
//        if (flag){
//            // flag = true:表示拿到锁！ 执行业务逻辑
//            // 获取到当前的num值
//            String num = redisTemplate.opsForValue().get("num");
//            // 判断
//            if (StringUtils.isEmpty(num)){
//                return;
//            }
//            // num不为空，需要对当前值进行+1操作，写会缓存
//            int numValue = Integer.parseInt(num);
//            // 写回缓存
//            redisTemplate.opsForValue().set("num",String.valueOf(++numValue));
//
//            // 释放锁
//            redisTemplate.delete("lock");
//        } else {
//            // 没有获取到锁
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            // 自旋
//            testLock();
//        }
//
//    }
    /**
     *  2.使用Redis做分布锁实现二
     *      redis给某个key设置设置过期时间命令 set key value; expire key timeout(不具备原子性)
     *      setex key timeout value;
     *      问题：可以会释放其他服务器锁
     */
//
//    @Override
//    public void testLock() {
//
//        // 使用setex命令 setex lock 2 ok
//        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "ok", 3, TimeUnit.SECONDS);
//        if (flag){
//            // flag = true:表示拿到锁！ 执行业务逻辑
//            // 获取到当前的num值
//            String num = redisTemplate.opsForValue().get("num");
//            // 判断
//            if (StringUtils.isEmpty(num)){
//                return;
//            }
//            // num不为空，需要对当前值进行+1操作，写会缓存
//            int numValue = Integer.parseInt(num);
//            // 写回缓存
//            redisTemplate.opsForValue().set("num",String.valueOf(++numValue));
//
//            // 释放锁
//            redisTemplate.delete("lock");
//        } else {
//            // 没有获取到锁
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            // 自旋
//            testLock();
//        }
//
//    }

    /**
     *  3.使用redis做分布锁实现方式三
     *      利用uuid防止误删锁
     *        set key uuid ex 3000 nx
     *        问题：删除操作缺乏原子性
     *        解决方案：使用lua脚本
     */
//    @Override
//    public void testLock() {
//
//        // 使用uuid防止误删锁
//        String uuid = UUID.randomUUID().toString();
//        // 使用setex命令 setex lock 2 uuid
//        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
//        if (flag){
//            // flag = true:表示拿到锁！ 执行业务逻辑
//            // 获取到当前的num值
//            String num = redisTemplate.opsForValue().get("num");
//            // 判断
//            if (StringUtils.isEmpty(num)){
//                return;
//            }
//            // num不为空，需要对当前值进行+1操作，写会缓存
//            int numValue = Integer.parseInt(num);
//            // 写回缓存
//            redisTemplate.opsForValue().set("num",String.valueOf(++numValue));
//
//            // 如果缓存中的uuid与当前uuid一致，则删除
//            if (redisTemplate.opsForValue().get("lock").equals(uuid)){
//                redisTemplate.delete("lock");
//            }
//
//        } else {
//            // 没有获取到锁
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            // 自旋
//            testLock();
//        }
//
//    }

    /**
     * 3.使用redis做分布锁实现方式四
     *  使用lua脚本 保证删除具有原子性
     *  缺点：lua在集群的情况下，不能保证删除的原子性
     */
//    @Override
//    public void testLock() {
//
//        // 使用uuid防止误删锁
//        String uuid = UUID.randomUUID().toString();
//        // 使用setex命令 setex lock 2 uuid
//        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
//        if (flag){
//            // flag = true:表示拿到锁！ 执行业务逻辑
//            // 获取到当前的num值
//            String num = redisTemplate.opsForValue().get("num");
//            // 判断
//            if (StringUtils.isEmpty(num)){
//                return;
//            }
//            // num不为空，需要对当前值进行+1操作，写会缓存
//            int numValue = Integer.parseInt(num);
//            // 写回缓存
//            redisTemplate.opsForValue().set("num",String.valueOf(++numValue));
//
//            // 定义一个lua脚本
//            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//
//            // 创建对象
//            DefaultRedisScript<Long> redisScript = new DefaultRedisScript();
//            // 设置lua脚本
//            redisScript.setScriptText(script);
//            redisScript.setResultType(Long.class);
//            // redis调用lua脚本
//            redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
//
//        } else {
//            // 没有获取到锁
//            try {
//                Thread.sleep(500);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            // 自旋
//            testLock();
//        }
//    }



}
